package com.demo.base.oss.helper;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.demo.base.oss.config.OssConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.UUID;

/**
 * @author 曳戈泰尔
 * @version 1.0
 * @description OSS工具类
 * @date 2023/3/10 7:37 PM
 */
@Component
@Slf4j
// 多例模式；如果ossClient做全局变量，需要启用多例模式；否则不启用多例模式，使用单例模式即可
// @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class OssHelper {

    /** 注入OSS配置Config */
    private OssConfig ossConfig;

    @Autowired
    public void setOssConfig(OssConfig ossConfig) {
        this.ossConfig = ossConfig;
    }

    /**
     * 上传到OSS服务器 自定义上传路经
     *
     * @param inStream 文件流
     * @param fileName 文件名称 包括后缀名
     * @param filePath 上传目标文件夹 example ： "file"
     * @param fileLength 文件长度，需要设置，否则可能出现上传文件不完整情况
     * @return
     */
    public String uploadFile2OSS(
            InputStream inStream, String fileName, String filePath, Long fileLength) {

        /* OSS客户端 */
        OSS ossClient =
                new OSSClientBuilder()
                        .build(
                                ossConfig.getEndPoint(),
                                ossConfig.getKeyId(),
                                ossConfig.getKeySecret());

        // 最终访问文件的路径
        String ret = null;

        try {

            // 判断bucket是否已经存在，不存在进行创建
            if (!doesBucketExist(ossClient)) {
                if (!createBucket(ossClient)) {
                    log.error("创建OSS文件桶失败");

                    return null;
                }
            }

            // 创建上传Object的Metadata
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setContentLength(inStream.available());
            objectMetadata.setCacheControl("no-cache");
            objectMetadata.setHeader("Pragma", "no-cache");

            // 获取文件后缀，通过截取字符串的形式
            objectMetadata.setContentType(
                    getContentType(fileName.substring(fileName.lastIndexOf("."))));

            objectMetadata.setContentDisposition("inline;filename=" + fileName);
            objectMetadata.setContentLength(fileLength);

            // 指定上传文件操作时是否覆盖同名Object。
            // 不指定x-oss-forbid-overwrite时，默认覆盖同名Object。
            // 指定x-oss-forbid-overwrite为false时，表示允许覆盖同名Object。
            // 指定x-oss-forbid-overwrite为true时，表示禁止覆盖同名Object，如果同名Object已存在，程序将报错。
            objectMetadata.setHeader("x-oss-forbid-overwrite", "false");

            // 对象名称
            String objectName;
            if (StringUtils.isBlank(filePath)) {
                objectName = ossConfig.getFileLocation() + "/" + fileName;
            } else {
                objectName = ossConfig.getFileLocation() + "/" + filePath + "/" + fileName;
            }

            // 上传文件
            ossClient.putObject(ossConfig.getBucketName(), objectName, inStream, objectMetadata);

            // 拼接 url 访问路径
            ret = ossConfig.getFileHost() + "/" + objectName;
            log.info("文件上传成功，文件名：{}，访问路径ret：{}", fileName, ret);

        } catch (Exception e) {
            log.error("OSS上传对象失败，原因：{}", e.getMessage());
        } finally {
            // 释放资源
            try {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
                if (inStream != null) {
                    inStream.close();
                }
            } catch (Exception e) {
                log.error("OSS中释放资源失败，原因：{}", e.getMessage());
            }
        }

        return ret;
    }

    /**
     * 上传到OSS服务器
     *
     * @param inStream 文件流
     * @param fileName 文件名称 包括后缀名
     * @return
     */
    public String uploadFile2OSS(InputStream inStream, String fileName, Long fileLength) {
        return uploadFile2OSS(inStream, fileName, "", fileLength);
    }

    /**
     * 上传文件到阿里云对象存储
     *
     * @param file 文件
     * @return
     */
    public String uploadFile2OSS(MultipartFile file) {

        try {
            // 获取文件名
            String fileName = file.getOriginalFilename();

            // 文件名不为空
            if (StringUtils.isBlank(fileName)) {
                log.error("获取MultipartFile文件名失败，文件名为空");

                return null;
            }

            // 获取文件后缀
            String suffix;
            try {
                suffix = fileName.substring(fileName.lastIndexOf("."));
            } catch (Exception ex) {
                log.error("获取MultipartFile文件后缀失败，原因：{}", ex.getMessage());

                return null;
            }

            // 生成不重复的新文件名
            String newFileName = UUID.randomUUID() + suffix;

            return uploadFile2OSS(file.getInputStream(), newFileName, file.getSize());

        } catch (Exception ex) {
            log.error("上传MultipartFile文件到OSS失败，原因：{}", ex.getMessage());

            return null;
        }
    }

    /**
     * 上传文件到阿里云对象存储
     *
     * @param file File
     * @return
     */
    public String uploadFile2OSS(File file) {

        InputStream inputStream = null;

        try {
            // 获取文件名
            String fileName = file.getName();

            // 文件名不为空
            if (StringUtils.isBlank(fileName)) {
                log.error("获取File文件名失败，文件名为空");

                return null;
            }

            // 获取文件后缀
            String suffix;
            try {
                suffix = fileName.substring(fileName.lastIndexOf("."));
            } catch (Exception ex) {
                log.error("获取File文件后缀失败，原因：{}", ex.getMessage());

                return null;
            }

            // 生成不重复的新文件名
            String newFileName = UUID.randomUUID() + suffix;

            // 获取文件输入流
            inputStream = new FileInputStream(file);

            return uploadFile2OSS(inputStream, newFileName, file.length());

        } catch (Exception ex) {
            log.error("上传File文件到OSS失败，原因：{}", ex.getMessage());

            return null;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("关闭File文件输入流失败，原因：{}", e.getMessage());
                }
            }
            if (file.exists()) {
                file.delete();
            }
        }
    }

    /**
     * 上传文件到阿里云对象存储 去除链接 ? 拼接的参数
     *
     * @param fileUrl 文件访问路径
     * @return
     */
    public String uploadFile2OSS(String fileUrl) {

        try {

            // 获取文件后缀
            String suffix;
            try {
                suffix = fileUrl.substring(fileUrl.lastIndexOf("."));
                // 去除后缀 ? 拼接的参数
                suffix = suffix.substring(0, suffix.indexOf("?"));
            } catch (Exception ex) {
                log.error("获取fileUrl文件后缀失败，原因：{}", ex.getMessage());

                return null;
            }

            // 生成不重复的新文件名
            String fileName = UUID.randomUUID() + suffix;

            // 获取上传文件文件流
            URL url = new URL(fileUrl);
            URLConnection urlConn = url.openConnection();
            InputStream inputStream = urlConn.getInputStream();

            return uploadFile2OSS(inputStream, fileName, urlConn.getContentLengthLong());

        } catch (Exception ex) {
            log.error("上传fileUrl文件到OSS失败，原因：{}", ex.getMessage());

            return null;
        }
    }

    /**
     * 根据文件名，删除OSS资源
     *
     * @param fileName 文件名，不包含路径
     * @return
     */
    public Boolean deleteFile(String fileName) {

        /* OSS客户端 初始化 */
        OSS ossClient =
                new OSSClientBuilder()
                        .build(
                                ossConfig.getEndPoint(),
                                ossConfig.getKeyId(),
                                ossConfig.getKeySecret());

        try {
            // 对象名称
            String objectName = ossConfig.getFileLocation() + "/" + fileName;

            // 删除文件或目录。如果要删除目录，目录必须为空。
            VoidResult voidResult = ossClient.deleteObject(ossConfig.getBucketName(), objectName);
            log.info("删除OSS结果状态：{}", voidResult.getResponse().getStatusCode());

        } catch (Exception ex) {
            log.error("OSS删除文件失败，原因：{}", ex.getMessage());

            return false;
        } finally {
            try {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            } catch (Exception e) {
                log.error("OSS中释放资源失败，原因：{}", e.getMessage());
            }
        }

        return true;
    }

    /**
     * 判断Bucket是否存在
     *
     * @return 存在返回true
     */
    public boolean doesBucketExist(OSS ossClient) {
        return ossClient.doesBucketExist(ossConfig.getBucketName());
    }

    /** 创建Bucket */
    public boolean createBucket(OSS ossClient) {

        try {
            CreateBucketRequest createBucketRequest =
                    new CreateBucketRequest(ossConfig.getBucketName());
            // 设置bucket权限为公共读，默认是私有读写
            createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
            // 设置bucket存储类型为低频访问类型，默认是标准类型
            createBucketRequest.setStorageClass(StorageClass.Standard);

            ossClient.createBucket(createBucketRequest);

            return true;

        } catch (Exception ex) {
            log.error("创建OSS的bucket失败，原因：{}", ex.getMessage());

            return false;
        }
    }

    /**
     * 通过文件名判断并获取OSS服务文件上传时文件的contentType
     *
     * @param fileName 文件名
     * @return 文件的contentType
     */
    public String getContentType(String fileName) {

        // 获取文件后缀
        String filenameExtension = fileName.substring(fileName.lastIndexOf("."));

        if (filenameExtension.equalsIgnoreCase(".bmp")) {
            return "application/x-bmp";
        }
        if (filenameExtension.equalsIgnoreCase(".gif")) {
            return "image/gif";
        }
        if (filenameExtension.equalsIgnoreCase(".jpeg")
                || filenameExtension.equalsIgnoreCase(".jpg")
                || filenameExtension.equalsIgnoreCase(".png")) {
            return "image/jpeg";
        }
        if (filenameExtension.equalsIgnoreCase(".html")) {
            return "text/html";
        }
        if (filenameExtension.equalsIgnoreCase(".txt")) {
            return "text/plain";
        }
        if (filenameExtension.equalsIgnoreCase(".vsd")) {
            return "application/vnd.visio";
        }
        if (filenameExtension.equalsIgnoreCase(".pptx")
                || filenameExtension.equalsIgnoreCase(".ppt")) {
            return "application/vnd.ms-powerpoint";
        }
        if (filenameExtension.equalsIgnoreCase(".docx")
                || filenameExtension.equalsIgnoreCase(".doc")) {
            return "application/msword";
        }
        if (filenameExtension.equalsIgnoreCase(".xla")
                || filenameExtension.equalsIgnoreCase(".xlc")
                || filenameExtension.equalsIgnoreCase(".xlm")
                || filenameExtension.equalsIgnoreCase(".xls")
                || filenameExtension.equalsIgnoreCase(".xlt")
                || filenameExtension.equalsIgnoreCase(".xlw")) {
            return "application/vnd.ms-excel";
        }
        if (filenameExtension.equalsIgnoreCase(".xml")) {
            return "text/xml";
        }
        if (filenameExtension.equalsIgnoreCase(".pdf")) {
            return "application/pdf";
        }
        if (filenameExtension.equalsIgnoreCase(".zip")) {
            return "application/zip";
        }
        if (filenameExtension.equalsIgnoreCase(".tar")) {
            return "application/x-tar";
        }
        if (filenameExtension.equalsIgnoreCase(".avi")) {
            return "video/avi";
        }
        if (filenameExtension.equalsIgnoreCase(".mp4")) {
            return "video/mpeg4";
        }
        if (filenameExtension.equalsIgnoreCase(".mp3")) {
            return "audio/mp3";
        }
        if (filenameExtension.equalsIgnoreCase(".mp2")) {
            return "audio/mp2";
        }

        return "application/octet-stream";
    }
}
